/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_pal_socket.h"
#include <stdbool.h>
#include <sys/select.h>
#include "hi_net_api.h"
#include "securec.h"
#include "ola_pal_os.h"

#define MS_PER_SEC  1000
#define US_PER_MS   1000
#define MAX(a, b)   (((a) > (b)) ? (a) : (b))

int OlaGetAddrInfo(const char *nodename, const char *servname,
    const OlaAddrInfo *hints, OlaAddrInfo **result)
{
    return getaddrinfo(nodename, servname, (struct addrinfo *)hints, (struct addrinfo **)result);
}

void OlaFreeAddrInfo(OlaAddrInfo *addrInfo)
{
    if (addrInfo != NULL) {
        freeaddrinfo((struct addrinfo *)addrInfo);
    }
}

int OlaSocket(OlaSocketType type)
{
    int domain = AF_INET;
    int protocol = IPPROTO_IP;
    if (type == OLA_SOCKET_TYPE_TCP) {
        type = SOCK_STREAM;
    } else {
        type = SOCK_DGRAM;
    }
    int fd = socket(domain, type, protocol);
    if (fd < 0) {
        return fd;
    }
    if (type == SOCK_DGRAM) {
        int flags = fcntl(fd, F_GETFL, 0);
        if (flags < 0) {
            close(fd);
            return -1;
        }
        unsigned int value = ((unsigned int)flags) | O_NONBLOCK;
        if (fcntl(fd, F_SETFL, (int)value) < 0) {
            (void)close(fd);
            return -1;
        }
    }

    return fd;
}

int OlaClose(int fd)
{
    return close(fd);
}

static int MuticastGroup(int fd, bool action)
{
    struct ip_mreq group;
    (void)memset_s((void *)&group, sizeof(struct ip_mreq), 0, sizeof(struct ip_mreq));

    group.imr_multiaddr.s_addr = inet_addr(OLA_MULTICAST_GROUP_ADDR4);
    group.imr_interface.s_addr = htonl(OLA_SOCKET_INADDR_ANY);
    int flag;
    if (action) {
        flag = IP_ADD_MEMBERSHIP;
    } else {
        flag = IP_DROP_MEMBERSHIP;
    }
    int ret = setsockopt(fd, IPPROTO_IP, flag, &group, sizeof(group));
    if (ret < 0) {
        printf("fd [%d] join muticast failed ret[%d]\r\n", fd, ret);
        return -1;
    }
    return 0;
}

static int SetSocketTimeout(int fd, bool isRead, unsigned int sec)
{
    struct timeval tv;
    tv.tv_sec = sec;
    tv.tv_usec = 0;
    int type = isRead ? SO_RCVTIMEO : SO_SNDTIMEO;
    int ret = setsockopt(fd, SOL_SOCKET, type, &tv, sizeof(struct timeval));
    if (ret < 0) {
        printf("set socket timeout fail\r\n");
        return -1;
    }

    return 0;
}

int OlaSetSocketOpt(int fd, OlaSocketOption option, const void *param)
{
    switch (option) {
        case OLA_SOCKET_OPTION_JOIN_MULTI_GROUP:
            return MuticastGroup(fd, true);
        case OLA_SOCKET_OPTION_REMOVE_MULTI_GROUP:
            return MuticastGroup(fd, false);
        case OLA_SOCKET_OPTION_READ_TIMEOUT:
            if (param != NULL) {
                return SetSocketTimeout(fd, true, *(unsigned int *)param);
            }
            break;
        case OLA_SOCKET_OPTION_SEND_TIMEOUT:
            if (param != NULL) {
                return SetSocketTimeout(fd, false, *(unsigned int *)param);
            }
            break;
        default:
            break;
    }
    return -1;
}

int OlaBind(int fd, const OlaSockaddr *addr)
{
    int opt = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) != 0) {
        printf("set SO_REUSEADDR failed\n");
        return -1;
    }
    return bind(fd, (struct sockaddr *)addr, sizeof(struct sockaddr));
}

int OlaConnect(int fd, const OlaSockaddr *addr)
{
    return connect(fd, (struct sockaddr *)addr, sizeof(struct sockaddr));
}

int OlaRecv(int fd, unsigned char *buf, unsigned short len)
{
    return recv(fd, buf, len, MSG_DONTWAIT);
}

int OlaSend(int fd, const unsigned char *buf, unsigned short len)
{
    return send(fd, buf, len, MSG_DONTWAIT);
}

int OlaRecvFrom(int fd, unsigned char *buf, unsigned short len, OlaSockaddr *from)
{
    unsigned int addrLen = sizeof(struct sockaddr);
    return recvfrom(fd, buf, len, 0, (struct sockaddr *)from, (socklen_t *)&addrLen);
}

int OlaSendTo(int fd, const unsigned char *buf, unsigned short len, const OlaSockaddr *to)
{
    return sendto(fd, buf, len, 0, (struct sockaddr *)to, sizeof(struct sockaddr));
}

static void GetFdSet(OlaFdSet *set, fd_set *fdSet, int *maxfd)
{
    if ((set != NULL) && (set->fdSet != NULL)) {
        FD_ZERO(fdSet);
        for (unsigned int i = 0; i < set->num; ++i) {
            FD_SET(set->fdSet[i], fdSet);
            *maxfd = MAX(*maxfd, set->fdSet[i]);
        }
    }
}

static void FdIsSet(OlaFdSet *set, fd_set *fdSet)
{
    if ((set == NULL) || (fdSet == NULL)) {
        return;
    }
    for (unsigned int i = 0; i < set->num; ++i) {
        if (FD_ISSET(set->fdSet[i], fdSet) == 0) {
            set->fdSet[i] = -1;
        }
    }
    return;
}

int OlaSelect(OlaFdSet *readSet, OlaFdSet *writeSet, OlaFdSet *exceptSet, unsigned int timeoutMs)
{
    int maxfd = -1;
    fd_set read, write, except;
    GetFdSet(readSet, &read, &maxfd);
    GetFdSet(writeSet, &write, &maxfd);
    GetFdSet(exceptSet, &except, &maxfd);

    struct timeval timeout;
    timeout.tv_sec = timeoutMs / MS_PER_SEC;
    timeout.tv_usec = timeoutMs % MS_PER_SEC * US_PER_MS;
    int ret = select(maxfd + 1, (readSet == NULL) ? NULL : &read,
        (writeSet == NULL) ? NULL : &write,
        (exceptSet == NULL) ? NULL : &except, &timeout);
    if (ret <= 0) {
        goto EXIT;
    }
    FdIsSet(readSet, &read);
    FdIsSet(writeSet, &write);
    FdIsSet(exceptSet, &except);
EXIT:
    return ret;
}

unsigned int OlaHtonl(unsigned int hostlong)
{
    return htonl(hostlong);
}

unsigned int OlaNtohl(unsigned int netlong)
{
    return ntohl(netlong);
}

unsigned short OlaHtons(unsigned short hostshort)
{
    return htons(hostshort);
}

unsigned short OlaNtohs(unsigned short netshort)
{
    return ntohs(netshort);
}

int OlaInetAton(const char *ip, unsigned int *addr)
{
    return inet_aton(ip, (struct in_addr *)addr);
}

char *OlaInetNtoa(unsigned int addr)
{
    struct in_addr tempAddr;
    tempAddr.s_addr = addr;
    return inet_ntoa(tempAddr);
}

unsigned int OlaInetAddr(const char *ip)
{
    return inet_addr(ip);
}

OlaSocketErrorCode OlaGetSocketError(int fd)
{
    int socketErr;
    unsigned int len = sizeof(socklen_t);
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &socketErr, (socklen_t *)&len) != 0) {
        return OLA_SOCKET_ERROR_OTHER;
    }

    switch (socketErr) {
        case 0:
            return OLA_SOCKET_ERROR_NO_ERROR;
        case EINTR:
            return OLA_SOCKET_ERROR_EINTR;
        case EAGAIN:
            return OLA_SOCKET_ERROR_EAGAIN;
        case EINPROGRESS:
            return OLA_SOCKET_ERROR_EINPROGRESS;
        default:
            printf("fd %d socket error %d\r\n", fd, socketErr);
            break;
    }

    return OLA_SOCKET_ERROR_OTHER;
}